#region Copyright Notice

// 
// 
//  
//  Copyright (c) 2009-2011 Anindya Chatterjee
//  
//  Licensed under the Apache License, Version 2.0 (the "License");
//  you may not use this file except in compliance with the License.
//  You may obtain a copy of the License at
//  
//      http://www.apache.org/licenses/LICENSE-2.0
//  
//  Unless required by applicable law or agreed to in writing, software
//  distributed under the License is distributed on an "AS IS" BASIS,
//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//  See the License for the specific language governing permissions and
//  limitations under the License.
// 
// 

#endregion

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Security.Permissions;
using AbstractClass.IO;
using AbstractClass.Linq;
using FileSystem = Microsoft.VisualBasic.FileIO.FileSystem;

namespace AbstractClass.Extensions
{
    /// <summary>
    /// Provides a set of extension methods for <see cref="System.IO"/> classes.
    /// </summary>
    public static class IOExtensions
    {
        /// <summary>
        /// Deletes all items in a directory recursively but does not delete the directory itself.
        /// </summary>
        /// <param name="dInfo">The directory to clear.</param>
        /// <returns><c>true</c> if all contents are deleted successfully; otherwise, <c>false</c>.</returns>
        [SecurityPermission(SecurityAction.LinkDemand, Unrestricted = true)]
        public static bool DeleteAllContent(this DirectoryInfo dInfo)
        {
            if (dInfo.Exists)
            {
                try
                {
                    foreach (FileSystemInfo item in dInfo.GetFileSystemInfos())
                    {
                        if (item is FileInfo)
                            item.Delete();
                        else
                        {
                            ((DirectoryInfo) item).DeleteAllContent();
                            ((DirectoryInfo) item).Delete(true);
                        }
                    }
                    return true;
                }
                catch (UnauthorizedAccessException)
                {
                    throw;
                }
                catch (Exception)
                {
                    return false;
                }
            }
            return false;
        }

        /// <summary>
        /// Returns an array of strongly typed <see cref="T:System.IO.FileSystemInfo"/> entries representing all the files and subdirectories in a directory.
        /// </summary>
        /// <param name="dInfo">The <see cref="DirectoryInfo"/> entry to enumerate.</param>
        /// <param name="searchOption">The search option.</param>
        /// <returns>
        /// An array of strongly typed <see cref="T:System.IO.FileSystemInfo"/> entries.
        /// </returns>
        /// <exception cref="T:System.IO.DirectoryNotFoundException">The path is invalid, such as being on an unmapped drive.
        /// </exception>
        [SecurityPermission(SecurityAction.LinkDemand, Unrestricted = true)]
        public static IEnumerable<FileSystemInfo> GetFileSystemInfos(this DirectoryInfo dInfo, SearchOption searchOption)
        {
            switch (searchOption)
            {
                case SearchOption.TopDirectoryOnly:
                    return dInfo.GetFileSystemInfos();
                case SearchOption.AllDirectories:
                    IEnumerable<FileSystemInfo> entries = GetAllContents(dInfo);
                    return entries.Except(dInfo);
                default:
                    throw new ArgumentOutOfRangeException("searchOption");
            }
        }

        [SecurityPermission(SecurityAction.LinkDemand, Unrestricted = true)]
        private static IEnumerable<FileSystemInfo> GetAllContents(FileSystemInfo fsInfo)
        {
            var queue = new Queue<FileSystemInfo>();
            queue.Enqueue(fsInfo);

            while (0 < queue.Count)
            {
                FileSystemInfo item = queue.Dequeue();
                if (item == null) continue;
                if (item is DirectoryInfo)
                {
                    foreach (FileSystemInfo fileSystemInfo in ((DirectoryInfo) item).GetFileSystemInfos())
                    {
                        queue.Enqueue(fileSystemInfo);
                    }
                }
                yield return item;
            }
        }

        /// <summary>
        /// Gets all the files in a directory and its subdirectory.
        /// </summary>
        /// <param name="dInfo">The directory to enumerate.</param>
        /// <param name="searchOption">The search option.</param>
        /// <returns>A list of all files under the specified directory.</returns>
        [SecurityPermission(SecurityAction.LinkDemand, Unrestricted = true)]
        public static IEnumerable<FileInfo> GetFiles(this DirectoryInfo dInfo, SearchOption searchOption)
        {
            return dInfo.GetFileSystemInfos(searchOption).OfType<FileInfo>();
        }

        /// <summary>
        /// Gets all sub-directories in a directory.
        /// </summary>
        /// <param name="dInfo">The directory to enumerate.</param>
        /// <param name="searchOption">The search option.</param>
        /// <returns>A list of all sub-directories under the specified directory.</returns>
        [SecurityPermission(SecurityAction.LinkDemand, Unrestricted = true)]
        public static IEnumerable<DirectoryInfo> GetDirectories(this DirectoryInfo dInfo, SearchOption searchOption)
        {
            return dInfo.GetFileSystemInfos(searchOption).OfType<DirectoryInfo>();
        }


        /// <summary>
        /// Searches a specific term in all readable files under a directory.
        /// </summary>
        /// <param name="directoryInfo">The directory to search within.</param>
        /// <param name="searchPattern">The search pattern.</param>
        /// <param name="ignoreCase">if set to <c>true</c> the search will be case-insensitive.</param>
        /// <param name="options">The search options.</param>
        /// <param name="fileWildCards">The type of files under the directory to search within.</param>
        /// <returns>A list of all files where the specific term is found.</returns>
        public static IEnumerable<FileInfo> FindInFiles(this DirectoryInfo directoryInfo, string searchPattern,
                                                        bool ignoreCase,
                                                        SearchOption options, params string[] fileWildCards)
        {
            ReadOnlyCollection<string> files;
            //if (fsInfo is FileInfo)
            //    throw new ArgumentException(
            //        LanguageResource.IOExtensionsCantSearchIntoFile, "fsInfo");

            switch (options)
            {
                case SearchOption.TopDirectoryOnly:
                    files = FileSystem.FindInFiles(directoryInfo.FullName, searchPattern, ignoreCase,
                                                   Microsoft.VisualBasic.FileIO.SearchOption.SearchTopLevelOnly,
                                                   fileWildCards);
                    break;
                case SearchOption.AllDirectories:
                    files = FileSystem.FindInFiles(directoryInfo.FullName, searchPattern, ignoreCase,
                                                   Microsoft.VisualBasic.FileIO.SearchOption.SearchAllSubDirectories,
                                                   fileWildCards);
                    break;
                default:
                    throw new ArgumentOutOfRangeException("options");
            }
            return files.Select(file => new FileInfo(file));
        }

        /// <summary>
        /// Gets the checksum of a stream.
        /// </summary>
        /// <param name="stream">The stream to get the checksum for.</param>
        /// <param name="checksumType">Type of the checksum.</param>
        /// <returns>Checksum data of the stream.</returns>
        public static string GetChecksum(this Stream stream, ChecksumType checksumType)
        {
            return ChecksumCalculator.GetChecksum(stream, checksumType);
        }

        /// <summary>
        /// Converts a stream into a byte array.
        /// </summary>
        /// <param name="input">The input stream to convert to a byte array.</param>
        /// <returns>A byte array representing the string.</returns>
        public static byte[] ToByteArray(this Stream input)
        {
            var totalStream = new byte[0];
            var buffer = new byte[32 * 1024];
            int read;

            while ((read = input.Read(buffer, 0, buffer.Length)) > 0)
            {
                var streamArray = new byte[totalStream.Length + read];
                totalStream.CopyTo(streamArray, 0);
                Array.Copy(buffer, 0, streamArray, totalStream.Length, read);
                totalStream = streamArray;
            }

            return totalStream;
        }

        /// <summary>
        /// Deletes a <see cref="FileSystemInfo"/> object and sends it to the recycle bin.
        /// </summary>
        /// <param name="fileSystemInfo">The object to delete.</param>
        [SecurityPermission(SecurityAction.LinkDemand, Unrestricted = true)]
        public static void SendToRecycleBin(this FileSystemInfo fileSystemInfo)
        {
            var shf = new SHFILEOPSTRUCT
                          {
                              hwnd = IntPtr.Zero,
                              wFunc = FileOperation.Delete,
                              fFlags = OperationFlag.AllowUndo | OperationFlag.NoConfirmation,
                              pFrom = fileSystemInfo.FullName + '\0' + '\0',
                              fAnyOperationsAborted = false,
                              hNameMappings = IntPtr.Zero
                          };

            NativeMethods.SHFileOperationW(ref shf);
        }
    }
}